package server

import (
	"context"
	"fmt"
	"os"
	"path"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/mount"
	"github.com/docker/go-connections/nat"

	econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/docker"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

func StartImageRegistry(name, image, imageRegistryPort, imageDataDirectory string) error {
	var err error
	if infrastructure.IsContainerd() && !infrastructure.IsDocker() {
		return startImageRegistryWithContainerd(name, image, imageRegistryPort, imageDataDirectory)
	}

	if !infrastructure.IsDocker() {
		log.BKEFormat(log.ERROR, "docker or containerd runtime not found.")
		return nil
	}
	certPath := fmt.Sprintf("/etc/docker/%s", name)
	err = generateConfig(certPath, imageRegistryPort)
	if err != nil {
		log.BKEFormat(log.ERROR, "Failed to generate config.")
		return err
	}
	err = global.Docker.EnsureImageExists(docker.ImageRef{Image: image}, utils.RetryOptions{MaxRetry: 3, Delay: 1})
	if err != nil {
		return err
	}
	serverRunFlag, err := global.Docker.EnsureContainerRun(name)
	if err != nil {
		return err
	}
	// 服务已经运行
	if serverRunFlag {
		log.BKEFormat(log.INFO, "The mirror warehouse service is already running. ")
		return nil
	}
	// Starting the image repository
	err = global.Docker.Run(
		&container.Config{
			Image: image,
			ExposedPorts: map[nat.Port]struct{}{
				"443/tcp": {},
			},
			Env: []string{"GODEBUG=x509ignoreCN=0"},
		},
		&container.HostConfig{
			Mounts: []mount.Mount{
				{
					Type:   mount.TypeBind,
					Source: imageDataDirectory,
					Target: "/var/lib/registry",
				},
				{
					Type:   mount.TypeBind,
					Source: certPath,
					Target: "/etc/docker/registry",
				},
			},
			PortBindings: map[nat.Port][]nat.PortBinding{
				nat.Port("443/tcp"): {
					{HostIP: "0.0.0.0",
						HostPort: imageRegistryPort,
					},
				},
			},
			RestartPolicy: container.RestartPolicy{
				Name:              "always",
				MaximumRetryCount: 0,
			},
		}, nil, nil, name)
	if err != nil {
		log.BKEFormat(log.WARN, "The image repository service fails to be deployed")
		return err
	}
	client := global.Docker.GetClient()
	for {
		log.BKEFormat(log.INFO, "Wait for the container mirroring service to start...")
		time.Sleep(5 * time.Second)
		containerInfo, err := client.ContainerInspect(context.Background(), name)
		if err != nil {
			continue
		}
		if containerInfo.ContainerJSONBase == nil {
			continue
		}
		if containerInfo.State.Running {
			break
		}
	}
	log.BKEFormat(log.INFO, "The container mirroring service is started by docker. ")
	return nil
}

func startImageRegistryWithContainerd(name, image, imageRegistryPort, imageDataDirectory string) error {
	certPath := fmt.Sprintf("/etc/docker/%s", name)
	err := generateConfig(certPath, imageRegistryPort)
	if err != nil {
		log.BKEFormat(log.ERROR, "Failed to generate config.")
		return err
	}
	err = econd.EnsureImageExists(image)
	if err != nil {
		return err
	}
	serverRunFlag, err := econd.EnsureContainerRun(name)
	if err != nil {
		return err
	}
	// 服务已经运行
	if serverRunFlag {
		log.BKEFormat(log.INFO, "The mirror warehouse service is already running. ")
		return nil
	}
	script := []string{
		"run", "-d", fmt.Sprintf("--name=%s", name),
		"-p", fmt.Sprintf("%s:443", imageRegistryPort),
		"--restart=always",
		"-v", fmt.Sprintf("%s:/var/lib/registry", imageDataDirectory),
		"-v", fmt.Sprintf("%s:/etc/docker/registry", certPath),
		image,
	}
	err = econd.Run(script)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("The image repository service fails to be deployed by containerd: %s", err))
		return err
	}

	for {
		log.BKEFormat(log.INFO, "Wait for the container mirroring service to start...")
		time.Sleep(5 * time.Second)
		info, err := econd.ContainerInspect(name)
		log.Debugf(info.Name, info.State)
		if err != nil {
			continue
		}
		if info.State.Running {
			break
		}
	}
	log.BKEFormat(log.INFO, "The container mirroring service is started by containerd. ")
	removeKey(certPath)
	return nil
}

func removeKey(certPath string) {
	keyPath := path.Join(certPath, "deploy.bocloud.k8s.key")
	if !fileExists(keyPath) {
		return
	}
	if err := os.Remove(keyPath); err != nil {
		log.BKEFormat(log.ERROR, fmt.Sprintf("remove %s failed %v", keyPath, err))
	}
	log.BKEFormat(log.INFO, fmt.Sprintf("remove %s success", keyPath))
}

func RemoveImageRegistry(name string) error {
	log.BKEFormat(log.INFO, "Remove the image repository")
	_ = os.RemoveAll("/etc/docker/" + name)
	if infrastructure.IsDocker() {
		for i := 0; i < 2; i++ {
			_ = global.Docker.ContainerRemove(name)
			_, exist := global.Docker.ContainerExists(name)
			if exist {
				time.Sleep(3 * time.Second)
			} else {
				break
			}
		}
		return nil
	}
	if infrastructure.IsContainerd() {
		for i := 0; i < 2; i++ {
			_ = econd.ContainerRemove(name)
			_, exist := econd.ContainerExists(name)
			if exist {
				time.Sleep(3 * time.Second)
			} else {
				break
			}
		}
		return nil
	}
	return nil
}

func generateConfig(certPath, port string) error {
	err := SetRegistryConfig(certPath)
	if err != nil {
		return err
	}
	err = SetServerCertificate(certPath)
	if err != nil {
		return err
	}
	err = SetClientCertificate(certPath, port)
	if err != nil {
		return err
	}
	err = SetClientLocalCertificate(certPath, port)
	if err != nil {
		return err
	}
	return nil
}
